/*
 * Copyright (c) 2001 - 2007 TOPP - www.openplans.org.  All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package ca.cbc.gis.util;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * This class extends the JSONBuilder to be able to write out geometric types.  It is coded
 * against the draft 5 version of the spec on http://geojson.org
 *
 * @author Chris Holmes, The Open Planning Project
 * @version $Id$
 *
 */
public enum GISGeometryBuilder {
	instance;
    /** Internal representation of OGC SF Point */
    protected static final int POINT = 1;

    /** Internal representation of OGC SF LineString */
    protected static final int LINESTRING = 2;

    /** Internal representation of OGC SF Polygon */
    protected static final int POLYGON = 3;

    /** Internal representation of OGC SF MultiPoint */
    protected static final int MULTIPOINT = 4;

    /** Internal representation of OGC SF MultiLineString */
    protected static final int MULTILINESTRING = 5;

    /** Internal representation of OGC SF MultiPolygon */
    protected static final int MULTIPOLYGON = 6;

    /** Internal representation of OGC SF MultiGeometry */
    protected static final int MULTIGEOMETRY = 7;

	/**
     * Writes any geometry object.  This class figures out which geometry representation to write
     * and calls subclasses to actually write the object.
     * @param geometry The geoemtry be encoded
     * @return The JSONBuilder with the new geoemtry
     * @throws JSONException If anything goes wrong
     */
    public Map<String, Object> buildGISGeometry(Geometry geometry) {
        final int geometryType = getGeometryType(geometry);
        Map<String, Object> map = new HashMap<String, Object>();
        
        if (geometryType != MULTIGEOMETRY) {
        	this.setType(map, geometry.getGeometryType());
        	
            switch (geometryType) {
            case POINT:
                Point point = (Point) geometry;
                this.setCoordinates(map, parseCoordinate(point.getX(), point.getY()));
                break;

            case LINESTRING:
                this.setCoordinates(map, parseCoordinates(((LineString) geometry).getCoordinateSequence()));
                break;

            case MULTIPOINT:
            	this.setCoordinates(map, parseCoordinates(geometry.getCoordinates()));
                break;

            case POLYGON:
                this.setCoordinates(map, parsePolygon((Polygon) geometry));
                break;

            case MULTILINESTRING:
            	List list1 = new ArrayList();
                for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
                    list1.add(parseCoordinates(((LineString) geometry.getGeometryN(i)).getCoordinateSequence()));
                }
                this.setCoordinates(map, list1);
                break;

            case MULTIPOLYGON:
            	List list2 = new ArrayList();
                for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
                    list2.add(parsePolygon((Polygon) geometry.getGeometryN(i)));
                }
                this.setCoordinates(map, list2);
                break;
            }
        } else {
        	this.setGeometries(map, parseGeomCollection((GeometryCollection) geometry));
        }

        return map;
    }
	
	/**
     * Gets the internal representation for the given Geometry
     *
     * @param geometry a Geometry
     *
     * @return int representation of Geometry
     */
    public int getGeometryType(Geometry geometry) {
        Class geomClass = geometry.getClass();
        int returnValue = -1;

        if (geomClass.equals(Point.class)) {
            returnValue = POINT;
        } else if (geomClass.equals(LineString.class)) {
            returnValue = LINESTRING;
        } else if (geomClass.equals(Polygon.class)) {
            returnValue = POLYGON;
        } else if (geomClass.equals(MultiPoint.class)) {
            returnValue = MULTIPOINT;
        } else if (geomClass.equals(MultiLineString.class)) {
            returnValue = MULTILINESTRING;
        } else if (geomClass.equals(MultiPolygon.class)) {
            returnValue = MULTIPOLYGON;
        } else if (geomClass.equals(GeometryCollection.class)) {
            returnValue = MULTIGEOMETRY;
        } else {
            returnValue = -1;
            //HACK!!! throw exception.
        }

        return returnValue;
    }
	
	private List parseCoordinate(double x, double y) {
        List list = new ArrayList(2);
        list.add(x);
        list.add(y);
    	return list;
    }
	
    private List parseCoordinates(Coordinate[] coords) {
        return parseCoordinates(new CoordinateArraySequence(coords));
    }

    /**
     * Write the coordinates of a geometry
     * @param coords The coordinates to write
     * @return this
     * @throws JSONException
     */
    private List parseCoordinates(CoordinateSequence coords) {
        final int coordCount = coords.size();
        List list = new ArrayList(coordCount);
        for (int i = 0; i < coordCount; i++) {
        	list.add(parseCoordinate(coords.getX(i), coords.getY(i)));
        }

        return list;
    }
    
    private List parseGeomCollection(GeometryCollection collection) {
        List list = new ArrayList();
        for (int i = 0, n = collection.getNumGeometries(); i < n; i++) {
            list.add(buildGISGeometry(collection.getGeometryN(i)));
        }
        return list;
    }

    /**
     * Writes a polygon
     * @param geometry The polygon to write
     * @throws JSONException
     */
    private List parsePolygon(Polygon geometry) {
    	List list = new ArrayList();
        list.add(parseCoordinates(geometry.getExteriorRing().getCoordinateSequence()));

        for (int i = 0, ii = geometry.getNumInteriorRing(); i < ii; i++) {
            list.add(parseCoordinates(geometry.getInteriorRingN(i).getCoordinateSequence()));
        }

        return list;
    }

    private void setCoordinates(Map<String, Object> map, List coor) {
		map.put("coordinates", coor);
	}

    private void setGeometries(Map<String, Object> map, List geo) {
		map.put("geometries", geo);
	}

    private void setType(Map<String, Object> map, String type) {
		map.put("type", type);
	}
}
